﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


#if EMBEDDED
    using OpenTK.Graphics.ES20;
#else
using OpenTK.Graphics.OpenGL;
#endif

namespace MonoStrategy.RenderSystem
{
    /// <summary>
    /// Renders a whole animation class.
    /// </summary>
    public class RenderableAnimationClass : RenderableVisual
    {
        private static List<NativeTexture> s_FrameCache = new List<NativeTexture>();

        private readonly List<RuntimeAnimSet> m_PlayingSets = new List<RuntimeAnimSet>();
        private Double m_AnimScale;
        private bool m_ResetTime = false;

        public Int64 MilliDiff { get; private set; }
        public Int64 StartTime { get; private set; }
        public AnimationClass Class { get; private set; }
        public IEnumerable<AnimationSet> PlayingSets { get { return m_PlayingSets.Select(e => e.Set); } }
        public Int32 FrozenFrameIndex { get; set; }
        public bool IsRepeated { get; set; }

        private class RuntimeAnimSet
        {
            public long Duration { get; private set; }
            public AnimationSet Set { get; private set; }
            public Animation[] Animations { get; private set; }

            public RuntimeAnimSet(AnimationSet inSet)
            {
                Set = inSet;
                Duration = inSet.DurationMillisBounded;
                Animations = inSet.Animations.OrderBy(e => e.RenderIndex).ToArray();
            }
        }

        public RenderableAnimationClass(TerrainRenderer inRenderer, AnimationClass inClass, CyclePoint inInitialPosition)
            : base(inRenderer, inInitialPosition)
        {
            Class = inClass;
            m_AnimScale = 1 / 20.0;
            AspectRatio = (Class.Width / (double)Class.Height);
            IsRepeated = true;

            SetupPositionShift();

            Play();
        }

        public RenderableAnimationClass(TerrainRenderer inRenderer, AnimationClass inClass, IPositionTracker inTracker)
            : base(inRenderer, inTracker.Position)
        {
            Class = inClass;
            m_AnimScale = 1/20.0;
            AspectRatio = (Class.Width / (double)Class.Height);
            IsRepeated = true;

            inTracker.OnPositionChanged += (unused, olsPos, newPos) => { Position = newPos; };

            SetupPositionShift();

            Play();
        }

        private void SetupPositionShift()
        {
            if ((Class.GroundPlane.Count == 1) &&
                ((Class.ShiftX != 0) || (Class.ShiftY != 0)) &&
                (AnimationLibrary.Instance.IsReadonly || AnimationLibrary.Instance.ForcePositionShift))
            {
                /* 
                 * Adjust animation shift to snap the ground plane point directly to its IPositionTracker. This
                 * will cause movables and foilage to be where it ought to be, instead of having its snap point
                 * at the top-left, which is precisly always wrong for those object types!
                 */
                PositionShiftX = (sbyte)-Class.GroundPlane[0].X;
                PositionShiftY = (sbyte)-Class.GroundPlane[0].Y;
            }
        }

        public void Play() { Play(new String[0]{}); }

        public void Play(params String[] inAnimSets)
        {
            AnimationSet[] sets = new AnimationSet[inAnimSets.Length];

            for (int i = 0; i < inAnimSets.Length; i++)
            {
                sets[i] = Class.FindSet(inAnimSets[i]);
            }

            Play(sets);
        }

        public void Pause()
        {
        }

        public void Play(params AnimationSet[] inAnimationSet)
        {
            lock (m_PlayingSets)
            {

                if (Class.Name == "WoodCutter")
                    Stop();

                Stop();

                foreach (var set in inAnimationSet.OrderBy(e => e.RenderIndex))
                {
#if DEBUG
                    if (!Class.Sets.Contains(set))
                        throw new ArgumentException("At least one of the given sets is not a member of this class.");
#endif

                    m_PlayingSets.Add(new RuntimeAnimSet(set));
                }
            }
        }

        public void Stop()
        {
            lock (m_PlayingSets)
            {
                m_PlayingSets.Clear();

                if (Class.AmbientSet != null)
                    m_PlayingSets.Add(new RuntimeAnimSet(Class.AmbientSet));
            }
        }

        public override void SetAnimationTime(Int64 inTime)
        {
            if (m_ResetTime)
            {
                m_ResetTime = false;
                StartTime = inTime;
            }

            MilliDiff = Math.Abs(inTime - StartTime);
        }

        public void ResetTime()
        {
            m_ResetTime = true;
        }

        internal override void Render(RenderPass inPass)
        {
            /*
             * Rendering is now done for all current frames with the same Z-Value.
             * This implies that the order of sets and animations within the animation
             * class has a direct impact on rendering (blending) order.
             */
            int frameCount = s_FrameCache.Count;

            foreach (var set in m_PlayingSets)
            {
                long duration = Math.Max(1, set.Duration);

                foreach (var anim in set.Animations)
                {
                    if (anim == null)
                        continue;

                    // select frame
                    Int32 frameIndex; 

                    if (anim.IsFrozen)
                    {
                        /*
                         * There shall only be one animation flagged frozen, but either way we
                         * make sure that index is within bounds
                         */
                        frameIndex = FrozenFrameIndex % anim.Frames.Count;
                    }
                    else
                    {
                        if ((MilliDiff >= duration) && !IsRepeated)
                            frameIndex = anim.Frames.Count - 1;
                        else
                            frameIndex  = (Int32)((MilliDiff % duration) * (anim.Frames.Count / (double)duration));
                    }

                    if (anim.Frames.Count == 0)
                        continue;

                    var frame = anim.Frames[frameIndex];

                    if (Renderer.SpriteEngine == null)
                    {
                        // this fallback is primarily intended for the AnimationEditor
                        while (frame.Index >= frameCount)
                        {
                            s_FrameCache.Add(null);
                            frameCount++;
                        }

                        // apply texture
                        var entry = s_FrameCache[frame.Index];

                        if (entry == null)
                        {
                            entry = s_FrameCache[frame.Index] = new NativeTexture(frame.Source);
                        }

                        Texture = entry;
                    }

                    // render frame
                    base.Render(
                        inPass,
                        frame.Index,
                        (anim.OffsetX + frame.OffsetX + anim.SetOrNull.Class.ShiftX) * m_AnimScale,
                        (anim.OffsetY + frame.OffsetY + anim.SetOrNull.Class.ShiftY) * m_AnimScale,
                        frame.Width * m_AnimScale,
                        frame.Height * m_AnimScale
                    );
                }
            }
        }
    }
}
